package cs301.msflags;

import java.io.Serializable;
import java.util.Vector;

import android.graphics.Canvas;
import android.graphics.Point;

/**
 * @author John Olennikov
 * @author Kyanne Mayfield
 * @author Allie Seibert
 * @author Emilia Holbik
 * @version 12/7/2012
 * 
 *          This class contains the information needed to draw a Minesweeper
 *          Flags board for playing.
 */

public class Board implements Serializable {

        /**
         * Variables to describe the spaceStatus variable in the BoardSpace class
         */
        private static int UNCOVERED = 0; 
        private static int EMPTY = 0;
        private static int MINE = 9;

        /**
         * Unique ID for Serializable classes
         */
        
        private static final long serialVersionUID = 8525444512711046562L;

        /**
         * The 2-dimensional array of board spaces which contains each of the
         * BoardSpace objects
         */
        
        private BoardSpace boardSpace[][];

        /**
         * The x-coordinate of the top left corner of the board
         */
        
        private int startX = 0;

        /**
         * The x-coordinate of the top left corner of the board
         */
        
        private int startY = 0;

        /**
         * The number of mines needed for this board
         */
        
        private int numMines = 0;

        /**
         * The number of rows needed for this board
         */
        
        private int height = 0;

        /**
         * The number of columns needed for this board
         */
        
        private int width = 0;

        /**
         * Whether or not the player has uncovered a mine, thus ending the game
         */
        
        private boolean uncoveredMine = false;
        
        /**
         * Whether or not the game has ended.
         */
        
        private boolean gameOver = false;

        /**
         * Initializes each instance variable and also calculates the starting X and
         * Y positions of the top left corner of the board.
         * 
         * @param initNumMines  number of mines chosen by user
         * @param initWidth  width chosen by user
         * @param initHeight  height chosen by user
         */
        public Board(int initNumMines, int initWidth, int initHeight) {
                this.numMines = initNumMines;
                this.height = initHeight;
                this.width = initWidth;
                boardSpace = new BoardSpace[width][height];
                for (int h = 0; h < height; ++h) {
                        for (int w = 0; w < width; ++w) {
                                boardSpace[w][h] = new BoardSpace(0, 0);
                        }
                }

                //after creating a board, insert its mines
                placeRandomMines();

        }

        /**
         * Calculates which BoardSpace has been touched
         * 
         * @param touchX  the x coordinate which the user touched
         * @param touchY  the y coordinate which the user touched
         * @return the touched space
         */

        public Point containsPoint(int touchX, int touchY) {
                //converts the x and y-coordinates touched to an integer identifying
                //a BoardSpace that was touched.
                int col = ((touchX - this.startX) - (touchX - this.startX)
                                % BoardSpace.SPACE_SIZE)
                                / BoardSpace.SPACE_SIZE;

                int row = ((touchY - this.startY) - (touchY - this.startY)
                                % BoardSpace.SPACE_SIZE)
                                / BoardSpace.SPACE_SIZE;

                //if the user touched within the bounds of the board
                if (row >= 0 && col >= 0 && col < width && row < height)
                        return new Point(col, row);
                else
                        return null;
        }

        /**
         * Randomly assigns the mines to designated board spaces
         */

        public void placeRandomMines() {
                int newMine = 0;
                int newHeight;
                int newWidth;
                boolean notAddedMineYet;

                //continues to add mines one by one until it has reached the number
                //of mines total for the board
                for (int i = 0; i < numMines; i++) {
                        notAddedMineYet = true;
                        while (notAddedMineYet) {
                                //randomly generates new x and y location for the mine
                                newMine = (newMine + (int) (height * width * Math.random()))
                                                % (height * width);
                                newHeight = newMine % height;
                                newWidth = (newMine + (int) (width * Math.random())) % width;

                                //if the location doesn't already have a mine, add a mine to
                                //that space and update the surrounding neighbors to display
                                //correct number of mines 
                                if (!boardSpace[newWidth][newHeight].hasMine()) {

                                        boardSpace[newWidth][newHeight].setSpaceValue(MINE);

                                        for (Point p : getNeighbors(newWidth, newHeight)) {
                                                if (p.x >= 0 && p.x < width && p.y >= 0 && p.y < height)
                                                        boardSpace[p.x][p.y].countNeighboringMine();
                                        }
                                        //if we successfully added a mine, break out the the while
                                        //loop
                                        notAddedMineYet = false;
                                }
                        }
                }

        }

        /**
         * Gets the neighboring BoardSpaces for which to update the number of 
         * surrounding mines.
         * 
         * @param thisSpaceX  x-coordinate of the BoardSpace with a mine
         * @param thisSpaceY  y-coordinate of the BoardSpace with a mine
         * @return all of the neighbors in a vector
         */

        public Vector<Point> getNeighbors(int thisSpaceX, int thisSpaceY) {

                // creates the boundaries of the neighboring points
                int maxX = thisSpaceX + 1;
                int maxY = thisSpaceY + 1;
                int minX = thisSpaceX - 1;
                int minY = thisSpaceY - 1;

                // adjusts appropriately if the boundaries exceed that of the board
                if (maxX >= width) {
                        maxX--;
                }
                if (maxY >= height) {
                        maxY--;
                }
                if (minX < 0) {
                        minX++;
                }
                if (minY < 0) {
                        minY++;
                }

                // adds all spaces within the boundaries and remove the point of the
                // current value then returns vector
                Vector<Point> neighbors = new Vector<Point>();
                for (int w = minX; w <= maxX; w++) {
                        for (int h = minY; h <= maxY; h++) {
                                neighbors.add(new Point(w, h));
                        }
                }
                neighbors.remove(new Point(thisSpaceX, thisSpaceY));
                return neighbors;
        }

        /**
         * Draws the board by calling upon each of the BoardSpaces to draw 
         * themselves.
         * 
         * @param canvas  on which to draw the board
         * @param screenHeight  Height of SurfaceView
         * @param screenWidth  Width of SurfaceView
         */
        public void draw(Canvas canvas, int screenHeight, int screenWidth) {

                //calculate where to draw each of the BoardSpaces
                this.startX = (screenWidth - (BoardSpace.SPACE_SIZE * width)) / 2;
                this.startY = (screenHeight - (BoardSpace.SPACE_SIZE * height)) / 2;

                //set the coordinates (in pixels) of each BoardSpace
                for (int h = 0; h < height; ++h) {
                        for (int w = 0; w < width; ++w) {
                                boardSpace[w][h].setCoordinate(startX + BoardSpace.SPACE_SIZE
                                                * w, startY + BoardSpace.SPACE_SIZE * h);
                        }
                }

                //draw each BoardSpace
                for (int h = 0; h < height; ++h) {
                        for (int w = 0; w < width; ++w) {
                                boardSpace[w][h].draw(canvas);
                        }
                }
        }

        /**
         * @return the array of BoardSpaces which make up the Board
         */
        public BoardSpace[][] getBoardSpaceArray() {
                return boardSpace;
        }

        /**
         * @return the number of mines the Board contains
         */
        public int getNumMines() {
                return this.numMines;
        }

        /**
         * @return the width of the board
         */
        public int getWidth() {
    		return width;
    	}
    
        /**
         * @return the height of the board
         */
    	public int getHeight() {
    		return height;
    	}
        
        /**
         * @return the area of the Board
         */
        public int getTotalNumSpaces() {
                return width * height;
        }

        /**
         * Handles flag action of the given board space by the given player.
         * 
         * @param player  which player flagged a space
         * @param hasFlag  whether the player has flags available to place
         * @param xPos  x-coordinate of the BoardSpace
         * @param yPos  y-coordinate of the BoardSpace
         * @return the number by which the player's amount of flags
         * 			remaining should be increased
         */
        public int flaggedByPlayer(int player, boolean hasFlag, int xPos, int yPos) {
                // Player 0 will always be flagging with blue and player 1 will always
                // be flagging with red.
                if (player == 0)
                        return boardSpace[xPos][yPos].flagBlue(hasFlag);
                else
                        return boardSpace[xPos][yPos].flagRed(hasFlag);
        }

        /**
         * Determines whether or not a space can be uncovered, 
         * and if so, uncovers it.
         * 
         * @param xPos  x-coordinate of BoardSpace which was touched
         * @param yPos  y-coordinate of BoardSpace which was touched
         * @return true if BoardSpace was uncovered, false otherwise.
         */
        public boolean uncoverSpace(int xPos, int yPos) {
                BoardSpace bs = boardSpace[xPos][yPos];

                //check conditions if it is possible for this space to be uncovered
                if (bs.isCovered() && !bs.isFlagged() && !bs.hasMine()) {
                        boardSpace[xPos][yPos].setSpaceStatus(UNCOVERED);
                        //if this space is empty, recursively uncover all the neighbors who
                        //are also empty and contain a number
                        if (bs.getSpaceValue() == EMPTY) {
                                for (Point p : getNeighbors(xPos, yPos)) {
                                        uncoverSpace(p.x, p.y);
                                }
                        }
                        return true;
                        //if a mine was uncovered, prepare to end the game
                } else if (bs.hasMine()) {
                        boardSpace[xPos][yPos].setSpaceStatus(UNCOVERED);
                        uncoveredMine = true;
                        return true;
                } else {
                        return false;
                }
        }

        /**
         * @return true if a mine was just uncovered, false otherwise.
         */
        public boolean getUncoveredMine() {
                return uncoveredMine;
        }
        
        /**
         * @param isGameOver  whether or not the game is over
         */
        public void setGameOver(boolean isGameOver) {
    		this.gameOver = isGameOver;
    	}
        
        /**
         * Determines the given player's score.
         * 
         * @param playerId  the id of the player whose score is
         * 					being determined
         * @return the score
         */
        public int getPlayerScore(int playerId) {
			//Count the number of correctly placed flags for each player.
			BoardSpace[][] boardSpaces = this.getBoardSpaceArray();
			int blueScore = 0;
			int redScore = 0;
			for (int w = 0; w < this.width; ++w) {
				for (int h = 0; h < this.height; ++h) {
					if (boardSpaces[w][h].isBlueFlagged() 
							&& boardSpaces[w][h].hasMine()) {
						++blueScore;
					} else if (boardSpaces[w][h].isRedFlagged()
							&& boardSpaces[w][h].hasMine()) {
						++redScore;
					}
				}
			}
        	
			// Return the relevant player's score.
        	if (playerId == 0) {
        		return blueScore;
        	}
        	else {
        		return redScore;
        	}
        }
}
